package com.iso8583;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class Iso8583Parser {
 public static Logger logger = LoggerFactory.getLogger(Iso8583Parser.class);
	public static Map<String, String> parse(String suffix, byte[] message,boolean isParseHead) throws Exception {
		Iso8583Configuration aaa = new Iso8583Configuration();
		Iso8583 configuration = aaa.getIso8583(suffix);
		InputStream is = new ByteArrayInputStream(message);
		Map<String, String> body = new LinkedHashMap<String,String>();
		if(isParseHead){
            parseHeader(configuration, is, body);
        }
		parseBody(configuration, is, body);
		return  body;
	}
	public static Map<String, String> parser(String suffix, byte[] message,boolean isParseHead) throws Exception {
		Iso8583Configuration aaa = new Iso8583Configuration();
		Iso8583 configuration = aaa.getIso8583(suffix);
		InputStream is = new ByteArrayInputStream(message);
		Map<String, String> body = new LinkedHashMap<String,String>();
		if(isParseHead){
			parseHead(configuration, is, body);
		}
		parseYinShengBody(configuration, is, body);
		return  body;
	}

	public static void parseHeader(Iso8583 configuration,
			InputStream is,Map<String, String> headMap) throws IOException, CodecException,
			Iso8583Exception {
		List<Item8583> items = configuration.getHead();
		if (items != null && items.size() > 0) {
			for (Item8583 item : items) {
					String value = parse(is, item);
					headMap.put(item.getPropertyName(),value );
			}
		}
	}
	public static void parseHead(Iso8583 configuration,
								   InputStream is,Map<String, String> headMap) throws IOException, CodecException,
			Iso8583Exception {
		List<Item8583> items = configuration.getHead();
		if (items != null && items.size() > 0) {
			for (Item8583 item : items) {
				if ("TPDU".equals(item.getPropertyName())){
					String value = parse(is, item);
					headMap.put(item.getPropertyName(),value );
				}
			}
		}
	}
	private static void parseBody(Iso8583 configuration,
			InputStream is,Map<String, String> fields) throws IOException, CodecException,
			Iso8583Exception {
		List<Item8583> items = configuration.getBody();
		Item8583 item = items.get(0);
		String value = parse(is, item);
		fields.put(item.getPropertyName(), value);
        item = items.get(1);
		if (item == null) {
			throw new Iso8583Exception("Bitmap field is missed.");
		}
		String bitMap = getBitMap(is);
		fields.put(item.getPropertyName(), bitMap);
		/*for (int i = 1; i < bitMap.length(); i++) {
			if (bitMap.charAt(i) == '1') {
				item = items.get(i + 1);
				value =parse(is, item);
				fields.put(item.getPropertyName(), value);
			}
		}*/
		System.out.print("bitmap包含的域：");
		for (int i = 1; i < bitMap.length(); i++) {
			if (bitMap.charAt(i) == '1') {
				item = items.get(i + 1);
				value =parse(is, item);
				System.out.print((i+1)+"|");
				//fields.put(item.getPropertyName(), value);
				fields.put(item.getFieldId(),value);
			}
		}
		System.out.println();
	}
	private static void parseYinShengBody(Iso8583 configuration,
								  InputStream is,Map<String, String> fields) throws IOException, CodecException,
			Iso8583Exception {
		List<Item8583> items = configuration.getBody();
		Item8583 item = items.get(0);
		String value = parseYinSheng(is, item);
		fields.put(item.getPropertyName(), value);
		item = items.get(1);
		if (item == null) {
			throw new Iso8583Exception("Bitmap field is missed.");
		}
		String bitMap = getBitMap(is);
		fields.put(item.getPropertyName(), bitMap);
		/*for (int i = 1; i < bitMap.length(); i++) {
			if (bitMap.charAt(i) == '1') {
				item = items.get(i + 1);
				value =parse(is, item);
				fields.put(item.getPropertyName(), value);
			}
		}*/
		System.out.print("bitmap包含的域：");
		for (int i = 1; i < bitMap.length(); i++) {
			if (bitMap.charAt(i) == '1') {
				item = items.get(i + 1);
				value =parseYinSheng(is, item);
				System.out.print((i+1)+"|");
				//fields.put(item.getPropertyName(), value);
				fields.put(item.getFieldId(),value);
			}
		}
		System.out.println();
	}

	private static String getBitMap(InputStream is) throws IOException,
			CodecException, Iso8583Exception {
		byte[] retBytes = new byte[8];
		int read = is.read(retBytes);
		if (read < 8) {
			throw new Iso8583Exception("Bitmap length is not enough, actual: "
					+ read + ", expected: 8");
		}

		// convert to hexMap
		String hexStr = AsciiCodec.bcd2AscStr(retBytes);

		// convert to bitMap
		String bitMap = AsciiCodec.hex2Binary(hexStr);

		if (bitMap.charAt(0) == '1') {
			String extHexStr;
			retBytes = new byte[8];
			read = is.read(retBytes);
			if (read < 8) {
				throw new Iso8583Exception(
						"1st bitmap length is not enough, actual: " + read
								+ ", expected: 8");
			}
			extHexStr = AsciiCodec.bcd2AscStr(retBytes);

			bitMap = bitMap + AsciiCodec.hex2Binary(extHexStr);
			hexStr += extHexStr;
		}

		return bitMap;
	}

	private static String parse(InputStream is, Item8583 field)
			throws IOException, CodecException, Iso8583Exception {
		int length = 0;
		String data_value = "";

		String data_type = field.getDataType();
		String length_type = field.getLengthType();
		if (length_type.equals(Iso8583Constants.LENGTH_TYPE_CONST)) {
			length = Integer.parseInt(field.getLength());
		} else if (length_type.equals(Iso8583Constants.LENGTH_TYPE_VAR2)) {
			length = getHeaderLen(is, 2, field);
		} else if (length_type.equals(Iso8583Constants.LENGTH_TYPE_VAR3)) {
			length = getHeaderLen(is, 3, field);
			
		} else {
			throw new Iso8583Exception("Length type for item [id: "
					+ field.getFieldId() + "] is invalid, expected '"
					+ Iso8583Constants.LENGTH_TYPE_CONST + "' or '"
					+ Iso8583Constants.LENGTH_TYPE_VAR2 + "' or '"
					+ Iso8583Constants.LENGTH_TYPE_VAR3 + "'");
		}

		if (data_type.equals(Iso8583Constants.DATA_TYPE_CHARASCII)
				|| data_type.equals(Iso8583Constants.DATA_TYPE_NUMASCII)) {
			byte[] retBytes = new byte[length];
			int read = is.read(retBytes);
			if (read < length) {
				throw new Iso8583Exception("Malformed message, field[id: "
						+ field.getFieldId()
						+ "]'s length is not enough, actual: " + read
						+ ", expected: " + length);
			}

			if (field.getConvertor() != null
					&& StringUtils.equals(field.getConvertor(), "hex")) {
				data_value = AsciiCodec.bcd2AscStr(retBytes);
			} else {
				data_value = new String(retBytes,"gbk");
			}
		} else {
			data_value = readBcd(is, length, 2, field);
		}

		return data_value;
	}
	private static String parseYinSheng(InputStream is, Item8583 field)
			throws IOException, CodecException, Iso8583Exception {
		int length = 0;
		String data_value = "";

		String data_type = field.getDataType();
		String length_type = field.getLengthType();
		if (length_type.equals(Iso8583Constants.LENGTH_TYPE_CONST)) {
			length = Integer.parseInt(field.getLength());
		} else if (length_type.equals(Iso8583Constants.LENGTH_TYPE_VAR2)) {
			length = getHeaderLen(is, 2, field);
		} else if (length_type.equals(Iso8583Constants.LENGTH_TYPE_VAR3)) {
			length = getHeaderLen(is, 3, field);

		} else {
			throw new Iso8583Exception("Length type for item [id: "
					+ field.getFieldId() + "] is invalid, expected '"
					+ Iso8583Constants.LENGTH_TYPE_CONST + "' or '"
					+ Iso8583Constants.LENGTH_TYPE_VAR2 + "' or '"
					+ Iso8583Constants.LENGTH_TYPE_VAR3 + "'");
		}

		if (data_type.equals(Iso8583Constants.DATA_TYPE_CHARASCII)
				|| data_type.equals(Iso8583Constants.DATA_TYPE_NUMASCII)) {
			byte[] retBytes = new byte[length];
			int read = is.read(retBytes);
			if (read < length) {
				throw new Iso8583Exception("Malformed message, field[id: "
						+ field.getFieldId()
						+ "]'s length is not enough, actual: " + read
						+ ", expected: " + length);
			}

			if (field.getConvertor() != null
					&& StringUtils.equals(field.getConvertor(), "hex")) {
				data_value = AsciiCodec.bcd2AscStr(retBytes);
			} else {
				data_value = new String(retBytes,"gbk");
			}
		} else {
			if ("35".equals(field.getFieldId())){
				length *= 2;
			}
			data_value = readBcd(is, length, 2, field);
		}

		return data_value;
	}

	private static int getHeaderLen(InputStream is, final int length,
			Item8583 field) throws IOException, CodecException,
			Iso8583Exception {
		if (StringUtils.equals(field.getVarType(),
				Iso8583Constants.VAR_TYPE_BIN)) {
			return Integer.parseInt(readBcd(is, length, 2, null));
		}
		if (StringUtils.equals(field.getVarType(),Iso8583Constants.VAR_TYPE_CHAR)){
			return Integer.parseInt(readBcd(is, length, 2, null))*2;
		}
		else {
			byte[] b = new byte[length];
			int read = is.read(b);
			if (read < length) {
				String id = field.getFieldId();
				throw new Iso8583Exception("Malformed message, field [id: "
						+ id + "]'s length is not enough, actual: " + read
						+ ", expected: " + length);
			}
			return Integer.parseInt(new String(b));
		}
	}

	private static String readBcd(InputStream is, final int length,
			final int radix, Item8583 field) throws IOException,
			CodecException, Iso8583Exception {
		int bcdLen = length / radix;
		int rsvLen = length % radix;
		byte[] b = null;

		if (rsvLen != 0) {
			bcdLen = bcdLen + 1;

			b = new byte[bcdLen];
			int read = is.read(b);
			if (read < bcdLen) {
				String id = field.getFieldId();
				throw new Iso8583Exception("Malformed message, field [id: "
						+ id + "]'s length is not enough, actual: " + read
						+ ", expected: " + bcdLen);
			}

			if (field != null
					&& StringUtils.equals(field.getAlignMode(),
					Iso8583Constants.ALIGN_MODE_LEFT)) {
				return StringUtils.substring(AsciiCodec.bcd2AscStr(b), 0,
						length);
			} else {
				return StringUtils.substring(AsciiCodec.bcd2AscStr(b), radix
						- rsvLen);
			}
		} else {
			b = new byte[bcdLen];
			int read = is.read(b);
			if (read < bcdLen) {
				String id = field.getFieldId();
				throw new Iso8583Exception("Malformed message, field [id: "
						+ id + "]'s length is not enough, actual: " + read
						+ ", expected: " + length);
			}
			return AsciiCodec.bcd2AscStr(b);
		}
	}
}
